﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConditionalPropertyUpdater.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst
{
    using System;
    using System.Linq.Expressions;
    using System.Reflection;

    using Umbraco.Core;

    /// <summary>
    /// Helper class that allow the update of properties only if they have changed.
    /// Useful for Umbraco old API that triggers updates on every property set.
    /// </summary>
    /// <typeparam name="T">
    /// The type of the target.
    /// </typeparam>
    public class ConditionalPropertyUpdater<T>
    {
        /// <summary>
        /// The target.
        /// </summary>
        private readonly T target;

        /// <summary>
        /// Initializes a new instance of the <see cref="ConditionalPropertyUpdater{T}"/> class.
        /// </summary>
        /// <param name="target">
        /// The target.
        /// </param>
        public ConditionalPropertyUpdater(T target)
        {
            this.target = target;
        }

        /// <summary>
        /// Gets the target.
        /// </summary>
        public T Target
        {
            get
            {
                return this.target;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="Target"/> has been changed.
        /// </summary>
        public bool HasBeenChanged { get; private set; }

        /// <summary>
        /// Updates a property only if the <paramref name="newValue"/> is different from current value.
        /// </summary>
        /// <param name="propertyExpression">
        /// The property expression.
        /// </param>
        /// <param name="newValue">
        /// The new value.
        /// </param>
        /// <typeparam name="TProperty">
        /// The type of the property.
        /// </typeparam>
        public void Update<TProperty>(Expression<Func<T, TProperty>> propertyExpression, TProperty newValue)
        {
            var propertyInfo = propertyExpression.GetPropertyInfo();
            var getMethod = propertyInfo.GetGetMethod(true);
            if (getMethod == null)
            {
                throw new UCodeFirstException(Resources.UnableToFindGetMethod.InvariantFormat(propertyInfo.Name));
            }

            var currentValue = (TProperty)getMethod.Invoke(this.target, null);

// ReSharper disable RedundantNameQualifier
            if (!object.Equals(currentValue, newValue))
// ReSharper restore RedundantNameQualifier
            {
                var setMethod = propertyInfo.GetSetMethod(true);
                if (setMethod == null)
                {
                    throw new UCodeFirstException(Resources.UnableToFindSetMethod.InvariantFormat(propertyInfo.Name));
                }

                setMethod.Invoke(this.target, new object[] { newValue });
                this.HasBeenChanged = true;
            }
        }

        /// <summary>
        /// Updates a property only if the <paramref name="newValue"/> is different from current value.
        /// </summary>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <param name="newValue">
        /// The new value.
        /// </param>
        public void Update(string propertyName, object newValue)
        {
            var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
            var getMethod = propertyInfo.GetGetMethod(true);
            if (getMethod == null)
            {
                throw new UCodeFirstException(Resources.UnableToFindGetMethod.InvariantFormat(propertyInfo.Name));
            }

            var currentValue = getMethod.Invoke(this.target, null);

            // ReSharper disable RedundantNameQualifier
            if (!object.Equals(currentValue, newValue))
            // ReSharper restore RedundantNameQualifier
            {
                var setMethod = propertyInfo.GetSetMethod(true);
                if (setMethod == null)
                {
                    throw new UCodeFirstException(Resources.UnableToFindSetMethod.InvariantFormat(propertyInfo.Name));
                }

                setMethod.Invoke(this.target, new[] { newValue });
                this.HasBeenChanged = true;
            }
        }
    }
}
